템플릿을 사용한 수많은 인수 타입에 대하여 동일한 구현을 사용할 수 있다.
하지만, 일부 인수 타입에 대하여서는 보다 효율적인 구현이 존재할 수 있다.
이 때, C++의 템플릿 특수화(Template Specialization)을 사용할 수 있다.
하나의 타입에 대한 클래스 특수화bool 타입에 대해 특수화를 통해 8개의 bool 값을 1바이트로 패킹(메모리 절약 가능)
template <typename T>
class vector{
public:
explicit vector(int size): my_size(size), data(new T[my_size]){}
vector(void): my_size(0) {}
private:
int my_size;
std::unique_ptr<T[]> data;
};
template <>
class vector<bool>{
};
특수화된 클래스는 더 이상 타입-매개변수가 아니지만, template 키워드와 빈 홑화살괄호를 필요로 한다.
(이는 이전에 정의된 기본 템플릿(Primary Tempalte)의 특수화 임을 선언)
만일 템플릿 매개변수 3개중 하나만 특수화하는 경우,
다른 두 개의 매개변수는 여전히 템플릿 매개변수로 선언해 주어야 함
template <typename T1, typename T2, typename T3>
class some_container{
};
template <typename T1, typename T3>
class some_container<T1, int, T3>{
};
기본 템플릿 벡터는 빈 벡터를 위한 생성자와 n개의 요소를 포함하는 생성자를 정의한다.
특수화한 템플릿도 일관성을 위해 동일한 생성자를 생성하여야 한다.
template <>
class vector<bool>{
public:
explicit vector(int size): my_size(size), data(new unsigned char[(my_size+7)/8]) {}
vector(void): my_size(0) {}
private:
int my_size;
std::unique_ptr<unsigned char[]> data;
};
위는 bool타입 8개를 1바이트(unsigned char)에 패킹하여서 저장한다.
디폴트 생성자가 기본 템플릿과 동일할 경우에도, 메서드가 특수화로 상속이 되지 않기 때문에 선언해 주어야 함
기본 템플릿에서 멤버 함수나 멤버 변수를 생략 가능하지만, 일관성을 위해 생략하지 않는 것이 좋음
bool 타입에는 덧셈 연산이 없기 때문에 operator+를 생략할 수 있다
상수 접근 연산자는 시프팅과 비트 마스킹을 이용해서 구현
template <>
class vector<bool>{
bool operator[](int i) const{
return (data[i/8]>>i&8)&1;
}
};
변경 가능한 접근 연산자(const가 아닌 메서드; mutable)는 단일 비트를 참조할 수 없어
단일 비트의 읽기 및 쓰기 작업을 제공하는 프록시(Proxy)를 사용해서 반환할 수 있다.
class vector_bool_proxy{
public:
vector_bool_proxy(unsigned char& byte, int p)
: byte(byte), mask(1<<p) {}
operator bool() const{ return byte&mask; }
vector_bool_proxy& operator=(bool b){
if(b) byte |= mask;
else byte &= ~mask;
return *this;
}
private:
unsigned char& byte;
unsigned char mask;
};
template<> class vector<bool>{
public:
vector(int size): my_size(size), data(new unsigned char[my_size]) {}
vector(void): my_size(0) {}
vector_bool_proxy operator[](int i){
return {data[i/8], i%8};
}
private:
int my_size;
std::unique_ptr<unsigned char[]> data;
};
함수 특수화 및 오버로딩함수 또한 클래스와 동일한 방식으로 특수화를 할 수 있다.
함수 템플릿 특수화는 오버로드 확인에 참여하지 않는다.
덜 구체적인 오버로드는 더 구체적인 템플릿 추상화보다 우선되기 때문에
함수 템플릿 특수화는 사용하지 않는 것이 좋다.(대신 오버로드로 구현하는 것이 더 좋음)
#include <cmath>
template <typename Base, typename Exponent>
Base inline power(const Base& x, const Exponent& y){
double inline power(double x, double y){
return std::pow(x, y);
}
모호함
template <typename Base, typename Exponent>
Base inline power(const Base& x, const Exponent& y);
template <typename Base>
Base inline power(const Base& x, int y);
template <typename Exponent>
double inline power(double x, const Exponent& y);
컴파일러는 인수 조합과 일치하는 모든 오버로드를 찾아서 가장 구체적인 오버로드를 선택한다.
power(3.0, 2u)는 첫 번째와 세 번째 오버로드와 일치하는데 더 구체적인 세 번째 오버로드를 선택한다.
만일 오버로드에서 power(3.0, 2)를 호출하면, 세 오버로드가 모드 일치하고
두 번째와 세 번째 오버로드에서 컴파일러는 호출을 결정하지 못한다.
모호함을 해결하기 위해서는 아래와 같은 더 명시적인 오버로드를 필요로 한다.
double inline power(double x, int y);
따라서 함수 템플릿의 특수화보다 명시적인 오버로드를 작성하는 것이 좋다
부분 특수화다른 템플릿 클래스에 템플릿 클래스를 특수화 하는 경우
기본 템플릿에 대하여 특수화를 진행할 때, 구현 중복과 새로운 타입의 무지를 피하기 위해
부분 특수화(Partial Specialization)을 사용한다.
template <>
class vector<complex<float>>;
template <>
class vector<complex<double>>;
template <>
class vector<complex<long double>>;
위와 같이 중복된 특수화에 대하여,
template <typename Real>
class vector<complex<Real>> { ... };
여러 매개변수가 있는 클래스의 부분 특수화
template <typename Value, typename Parameters>
class vector<sparese_matrix<Value, Parameters> > { ... };
포인터 특수화
template <typename T>
class vector<*T>{ ... };
부분 템플릿 특수화는 정규 템플릿 특수화와 결합하여 사용할 수 있다.이를 완전 특수화(Full Specialization)이라고 한다.
완전 특수화가 부분 특수화보다 우선 순위가 더 높다.
서로 다른 부분을 특수화하는 오버로드의 경우, 가장 구체적인 오버로드를 선택한다.
template <typename Value, typename Parameters>
class vector<sparse_matrix<Value, Parameters> > { ... };
template <typename Parameters>
class vector<sparse_matrix<float, Parameters> > { ... };
C++11을 지원하지 않는 컴파일러의 경우 >와 > 사이에 공백이 필요하다.
공백 없이 >>로 쓰면 컴파일러는 시프트 연산자로 인식한다.
부분 특수화된 함수함수 템플릿은 부분 특수화를 진행할 수 없다.
하지만 완전 특수화에서는 오버로드를 사용해 특별한 구현을 제공한다.
함수 템플릿이 일치할 경우, 우선순위가 높도록 보다 구체적인 함수 템플릿을 선택한다.
template <typename T>
inline T abs(const T& x){
return x<T(0)?-x:x;
}
template <typename T>
ineline T abs(const std::complex<T>& x){
return sqrt(real(x)*real(x)+imag(x)+imag(x));
}
함수 템플릿의 오버로딩 구현은 쉽고 합리적으로 잘 동작하지만,
대규모 프로젝트에서 여러 파일이 분산된 경우, 오버로드의 경우 의도한 오버로드를 호출하지 않을 수 있다.
(템플릿 함수와 템플릿이 아닌 함수의 혼합으로 오버로드 확인과 쉽지 않은 네임스페이스 확인의 자명 때문)
예측 가능한 특수화 동작을 확인하기 위해서, 클래스 템플릿 특수화 측면에서 내부적으로 구현하는 방법이가장 안전하며 단일 함수 템플릿은 사용자 인터페이스로 제공해야 한다.
특수화의 리턴 타입이 다른 경우
template <typename T> struct abs_functor;
template <typename T>
decltype(auto) abs(const T& x){
return abs_functor<T>()(x);
}
abs 제네릭 함수는 abs_functor<T>()를 생성하고, 인수 x로 연산자를 ()를 호출한다.
(이는 abs_functor<T>(x)라 다른 동작임, abs_functor<T>(x)는 abs_functor<T>의 생성자에 x를 전달
abs_functor<T>()(x)는 abs_functor<T> default 생성자를 호출하고, abs_functor에 정의된
해당 인스턴스에 () 연산자를 x인자로 호출함 )
일반 특수화를 const 또는 레퍼런스로 한정할 수 있는 경우, decltype(auto)를 사용해서 한정자를 전달
template <typename T>
auto abs(const T& x) -> decltype(abs_functor<T>()(x)){
return abs_functor<T>()(x);
}
C++03에서는 리턴 타입에 타입 추론을 할 수 없다(auto)
따라서 명시적으로 선언해 주어야 한다.
template <typename T>
typename abs_functor<T>::result_type abs(const T& x){
return abs_functor<T>()(x);
}
complex<T>에 대한 부분 특수화한 functor 함수
template <typename T>
struct abs_functor{
typedef T result_type;
T operator()(const T& x){
return x<T(0)?-x:x;
}
};
template <typename T>
struct abs_functor<std::complex<T> >{
typedef T result_type;
T operator()(const std::complex<T>& x){
return sqrt(real(x)*real(x)+imag(x)*imag(x));
}
};